package wx

import (
	"encoding/json"
	"errors"
	"fmt"
	web "geektime-go-homework/web/class4"
	"github.com/google/uuid"
	"github.com/patrickmn/go-cache"
	"html/template"
	"io"
	"net/http"
	"time"
)

type AuthorizeServer struct {
	Name string `json:"name"`
	Url  string `json:"url"`
}

var AuthorizeServerList map[string]AuthorizeServer
var wxLoginSession = cache.New(time.Minute*15, time.Second)
var authorizeSession = cache.New(time.Minute*15, time.Second)
var wxTokenStorage = cache.New(time.Minute*15, time.Second)
var wxThirdStorage = cache.New(time.Minute*15, time.Second)
var wxCookieKey = "wx_sess_id"
var thirdCookieKey = "third_sess_id"

type wxThirdLoginToken struct {
	AccessToken  string        `json:"access_token"`
	RefreshToken string        `json:"refresh_token"`
	ExpireAt     time.Duration `json:"expire_at"`
}

type WxSessionUserInfo struct {
	NickName string `json:"nick_name"`
	Region   string `json:"region"`
}

type CodeStruct struct {
	Code     string
	ClientId string
}

// authorizeServerApply
//
//	@Description: 申请验证服务器授权
func authorizeServerApply() {
	AuthorizeServerList = make(map[string]AuthorizeServer)

	// 提前注册申请client_id wxwaiyouxi123123
	AuthorizeServerList["wxwaiyouxi123123"] = AuthorizeServer{
		Name: "歪游戏",
		Url:  "http://hahagame.com:8201/token",
	}
}

func StartWxServer() {
	authorizeServerApply()
	tpl, err := template.ParseGlob("./wx/template/*.gohtml")
	if err != nil {
		panic(err)
	}
	engine := &web.GoTemplateEngin{
		T: tpl,
	}
	server := web.NewHttpServer(web.ServerWithTemplateEngine(engine))

	server.Get("/authorize", func(ctx *web.Context) {
		err2 := WxLoginVerify(ctx)
		if err2 != nil {
			return
		}
		cookie, _ := ctx.Req.Cookie(wxCookieKey)
		ssid := cookie.Value
		loginInfo, _ := wxLoginSession.Get(ssid)
		userInfo := loginInfo.(WxSessionUserInfo)

		clientId, _ := ctx.QueryValue("client_id").AsString()
		authorizeInfo := AuthorizeServerList[clientId]
		uri := "/authorize?" + ctx.Req.URL.Query().Encode()
		_ = ctx.Render("wxAuth.gohtml", map[string]string{"NickName": userInfo.NickName, "Region": userInfo.Region, "AppName": authorizeInfo.Name, "URI": uri})
		return
	})

	server.Post("/authorize", func(ctx *web.Context) {
		err2 := WxLoginVerify(ctx)
		if err2 != nil {
			return
		}
		clientId, err := ctx.QueryValue("client_id").AsString()
		if err != nil {
			panic(err)
		}
		authorizeInfo := AuthorizeServerList[clientId]
		//authorizeInfo.Url
		// 生成一次性校验code
		code := uuid.New().String()
		authorizeSession.Set(code, CodeStruct{
			Code:     code,
			ClientId: clientId,
		}, 5*time.Minute)
		cookie, _ := ctx.Req.Cookie(wxCookieKey)
		wxSessId := cookie.Value
		wxTokenStorage.Set(code, wxSessId, 5*time.Minute)
		http.Redirect(ctx.Resp, ctx.Req, authorizeInfo.Url+"?code="+code, 302)
	})

	server.Post("/code/verify", func(ctx *web.Context) {

		// 这一步本来应该让客户端携带 client_id + appsecret等信息验证签名，但是我懒
		bodyBs, _ := io.ReadAll(ctx.Req.Body)
		bodyStruct := struct {
			Code string `json:"code"`
		}{}
		json.Unmarshal(bodyBs, &bodyStruct)
		code := bodyStruct.Code
		_, ok := authorizeSession.Get(code)
		if !ok {
			ctx.RespJson(http.StatusBadRequest, nil)
			return
		}

		tokenInfo := wxThirdLoginToken{
			AccessToken:  uuid.New().String(),
			RefreshToken: uuid.New().String(),
			ExpireAt:     10 * time.Minute,
		}

		// 这里可以加上验证code

		// 删除一次性code
		authorizeSession.Delete(code)
		// 设置accessToken
		wxTokenStorage.Set(tokenInfo.AccessToken, tokenInfo, tokenInfo.ExpireAt)
		// 获取用户信息
		ssid, _ := wxTokenStorage.Get(code)
		loginInfo, _ := wxLoginSession.Get(ssid.(string))
		userInfo := loginInfo.(WxSessionUserInfo)
		wxThirdStorage.Set(tokenInfo.AccessToken, userInfo, tokenInfo.ExpireAt)
		wxTokenStorage.Set(tokenInfo.RefreshToken, tokenInfo, 5*time.Minute)
		ctx.RespJson(http.StatusOK, tokenInfo)

		ctx.SetCookie(&http.Cookie{
			Name:  thirdCookieKey,
			Value: ssid.(string),
		})

		wxLoginSession.Delete(ssid.(string))
		return
	})

	server.Get("/userInfo", func(ctx *web.Context) {
		accessToken, _ := ctx.QueryValue("access_token").AsString()
		loginInfo, ok := wxThirdStorage.Get(accessToken)
		if !ok {
			ctx.RespServerError("没有用户信息")
			return
		}
		userInfo := loginInfo.(WxSessionUserInfo)
		ctx.RespJson(http.StatusOK, userInfo)
		return

	})

	server.Get("/login", func(ctx *web.Context) {
		_ = ctx.Render("login.gohtml", nil)
		return
	})

	server.Post("/login", func(ctx *web.Context) {
		username, _ := ctx.FormValue("username")
		password, _ := ctx.FormValue("password")
		if username == "kalv1n" && password == "123456" {
			//登录成功
			wxSessId := uuid.New().String()
			// 24小时不用再次登录微信
			wxLoginSession.Set(wxSessId, WxSessionUserInfo{
				NickName: "kalv1n",
				Region:   "广州",
			}, 24*time.Hour)
			// 设置cookie
			ctx.SetCookie(&http.Cookie{
				Name:  wxCookieKey,
				Value: wxSessId,
			})
			wxHomePage := fmt.Sprintf("http://wxauth.com:8202/home")
			http.Redirect(ctx.Resp, ctx.Req, wxHomePage, http.StatusFound)
			return
		}
		// 登录失败
		ctx.RespServerError("用户账号名密码不对")
	})

	server.Get("/home", func(ctx *web.Context) {
		err2 := WxLoginVerify(ctx)
		if err2 != nil {
			return
		}
		cookie, _ := ctx.Req.Cookie(wxCookieKey)
		ssid := cookie.Value
		loginInfo, _ := wxLoginSession.Get(ssid)
		userInfo := loginInfo.(WxSessionUserInfo)
		_ = ctx.Render("wxHomePage.gohtml", map[string]string{"NickName": userInfo.NickName, "Region": userInfo.Region})
		return
	})
	err = server.Start(":8202")
}

// WxLoginVerify
//
//	@Description: 验证微信登录
//	@param ctx
func WxLoginVerify(ctx *web.Context) error {
	// 获取Query透传
	wxLoginPage := fmt.Sprintf("http://wxauth.com:8202/login")
	cookie, err := ctx.Req.Cookie(wxCookieKey)
	if err != nil {
		http.Redirect(ctx.Resp, ctx.Req, wxLoginPage, http.StatusFound)
		return errors.New("未登录")
	}
	ssid := cookie.Value
	_, ok := wxLoginSession.Get(ssid)
	if !ok {
		println("微信未登录")
		http.Redirect(ctx.Resp, ctx.Req, wxLoginPage, http.StatusFound)
		return errors.New("未登录")
	}
	return nil
}
